package com.gitee.wsl.net.downloader.connection

import com.gitee.wsl.ext.map.tran
import com.gitee.wsl.net.client.proxy
import com.gitee.wsl.net.client.sockProxy
import com.gitee.wsl.net.downloader.connection.proxy.ProxyStrategy
import com.gitee.wsl.net.downloader.connection.proxy.ProxyStrategyProvider
import com.gitee.wsl.net.downloader.connection.proxy.ProxyType
import com.gitee.wsl.net.downloader.connection.response.ResponseInfo
import com.gitee.wsl.net.downloader.downloaditem.IDownloadCredentials
import com.gitee.wsl.net.header
import com.gitee.wsl.transform.encode.base.encodeBase64ToString
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.providers.BasicAuthCredentials
import io.ktor.client.plugins.auth.providers.DigestAuthCredentials
import io.ktor.client.plugins.auth.providers.basic
import io.ktor.client.plugins.auth.providers.digest
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.request
import io.ktor.http.contentLength
import io.ktor.http.headers
import io.ktor.util.toMap
import io.ktor.utils.io.InternalAPI
import kotlin.jvm.JvmStatic

class KtorDownloaderClient(private val client: HttpClient,
                           private val proxyStrategyProvider: ProxyStrategyProvider,
) : DownloaderClient() {

    private suspend fun newCall(
        downloadCredentials: IDownloadCredentials,
        start: Long,
        end: Long?,
        extraBuilder: HttpClientConfig<*>.() -> Unit={},
    ):HttpResponse{
        val rangeHeader = createRangeHeader(start, end)
        return client
            .applyProxy(downloadCredentials)
            .config {
            headers {
                defaultHeadersInFirst().forEach { (k, v) ->
                    header(k, v)
                }
                downloadCredentials.headers
                    ?.filter {
                        //OkHttp handles this header and if we override it,
                        //makes redirected links to have this "Host" instead of their own!, and cause error
                        !it.key.equals("Host", true)
                    }
                    ?.forEach { (k, v) ->
                        header(k, v)
                    }
                defaultHeadersInLast().forEach { (k, v) ->
                    header(k, v)
                }

                downloadCredentials.userAgent?.let { userAgent ->
                    header("User-Agent", userAgent)
                }
                header(rangeHeader.first,rangeHeader.second)
            }
            val username = downloadCredentials.username
            val password = downloadCredentials.password
            if (username?.isNotBlank() == true && password?.isNotBlank() == true) {
                //header("Authorization", Credentials.basic(username, password))
                Auth {
                  when(downloadCredentials.authType){
                      "digest"->{
                          digest {
                              credentials {
                                  DigestAuthCredentials(username = username, password = password)
                              }
                          }
                      }
                      else->{
                          basic {
                              credentials {
                                  BasicAuthCredentials(username = username, password = password)
                              }
                          }
                      }
                  }
                }
            }
            extraBuilder()
        }.get(downloadCredentials.link)
    }

    override suspend fun head(credentials: IDownloadCredentials): ResponseInfo {
        newCall(
            downloadCredentials = credentials,
            start = 0,
            end = 255,
            extraBuilder = {
//                head()
            }
        ).let { response ->
//            println(response.headers)
            return createFileInfo(response)
        }
    }



    @OptIn(InternalAPI::class)
    override suspend fun connect(
        credentials: IDownloadCredentials,
        start: Long,
        end: Long?,
    ): Connection {
        val response = newCall(
            downloadCredentials = credentials,
            start = start,
            end = end,
            extraBuilder = {
                //get()
            }
        )
        val body = runCatching {
            requireNotNull(response.rawContent) {
                "body is null"
            }
        }.onFailure {
            //response.close()
        }.getOrThrow()
        return Connection(
            source = body.readBuffer,
            contentLength = response.contentLength()!!,
            closeable = body.readBuffer,
            responseInfo = createFileInfo(response)
        )
    }

    private fun createFileInfo(response: HttpResponse): ResponseInfo {
        return ResponseInfo(
            statusCode = response.status.value,
            message = response.status.description,
            requestUrl = response.request.url.toString(),
            requestHeaders = response.request.headers.toMap().tran {(key, value) -> key.lowercase() to value.joinToString()  },
            responseHeaders = response.headers.toMap().tran { (key, value) ->
                key.lowercase() to value.joinToString()
            },
        )
    }

    private fun HttpClient.applyProxy(
        downloadCredentials: IDownloadCredentials,
    ): HttpClient = config {

        when (
            val strategy = proxyStrategyProvider.getProxyStrategyFor(downloadCredentials.link)
        ) {
            /*ProxyStrategy.Direct -> return this
            ProxyStrategy.UseSystem -> {
                newBuilder()
                    .proxySelector(ProxySelector.getDefault())
                    .build()
            }*/

            is ProxyStrategy.ManualProxy -> {

                    when (strategy.proxy.type) {
                        ProxyType.HTTP -> proxy(strategy.proxy.host)
                        ProxyType.SOCKS -> sockProxy(strategy.proxy.host,strategy.proxy.port)
                    }


                /*return newBuilder()
                    .proxy(
                        Proxy(
                            when (proxy.type) {
                                ProxyType.HTTP -> Proxy.Type.HTTP
                                ProxyType.SOCKS -> Proxy.Type.SOCKS
                            },
                            InetSocketAddress(proxy.host, proxy.port)
                        )
                    ).let {
                        if (proxy.username != null && proxy.type == ProxyType.HTTP) {
                            it.proxyAuthenticator { _, r ->
                                val credentials = Credentials.basic(
                                    proxy.username,
                                    proxy.password.orEmpty()
                                )
                                r.request
                                    .newBuilder()
                                    .header("Proxy-Authorization", credentials)
                                    .build()
                            }
                        } else {
                            it
                        }
                    }.build()*/
            }

            ProxyStrategy.Direct -> TODO()
            ProxyStrategy.UseSystem -> TODO()
        }
    }

}

/** Factory for HTTP authorization credentials. */
object Credentials {
    /** Returns an auth credential for the Basic scheme. */
    @JvmStatic
    fun basic(
        username: String,
        password: String
    ): String {
        val usernameAndPassword = "$username:$password"
        val encoded = usernameAndPassword.encodeBase64ToString()
        return "Basic $encoded"
    }
}